package midcode;

import lexical.Key;
import lexical.Word;
import node.Node;
import optimizer.Optimizer;
import optimizer.RegPool;
import symbol.Symbol;
import symbol.SymbolTable;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class MidcodeGenerator {
    private final String globalStart = "####################Global Start####################";
    private final String globalEnd = "##########Global End##########";
    private final String asciizStart = "###############Asciiz Start###############";
    private final String asciizEnd = "##########Asciiz End##########";
    private final String funcEnd = "##########Func End##########";
    private final String funcStart = "####################Func Start####################";
    private static MidcodeGenerator INSTANCE = null;
    private final Node compUnit;
    private final SymbolTable table;
    private final ArrayList<MidCode> globalDecls = new ArrayList<>();
    private final ArrayList<MidCode> midCodes = new ArrayList<>();
    private final ArrayList<MidStr> midStrs = new ArrayList<>();
    private String funcName = "global";
    private int tmp_num = 0;
    private int str_num = 0;
    private int label_num = 0;
    private int offset_sp = 4;
    private int offset_gp = -0x8000;
    private int preSize = 4;
    private int stack_size = 0;

    private HashMap<String, ArrayList<Integer>> tmpMap = new HashMap<>();
    private RegPool regPool;

    private MidcodeGenerator(Node compUnit, SymbolTable table) {
        this.compUnit = compUnit;
        this.table = table;
    }

    public static void Init(Node compUnit, SymbolTable table) {
        INSTANCE = new MidcodeGenerator(compUnit, table);
    }

    public static MidcodeGenerator getInstance() {
        return INSTANCE;
    }

    public SymbolTable getSymbolTable() {
        return table;
    }

    public ArrayList<MidCode> getGlobalDecls() {
        return globalDecls;
    }

    public ArrayList<MidCode> getMidCodes() {
        return midCodes;
    }

    public ArrayList<MidStr> getMidStrs() {
        return midStrs;
    }

    public void addGlobalDecl(MidCode midCode) {
        globalDecls.add(midCode);
    }

    public void addMidcode(MidCode midcode) {
        midCodes.add(midcode);
    }

    public String getFuncName() {
        return funcName;
    }

    public int getOffset_sp() {
        return offset_sp;
    }

    public void addOffset_sp(int offset) {
        this.offset_sp += offset;
    }

    public int getOffset_gp() {
        return offset_gp;
    }

    public void addOffset_gp(int offset) {
        this.offset_gp += offset;
    }

    public void addStackSize(int size) {
        stack_size += size;
    }

    public void subStackSize(int size) {
        stack_size -= size;
    }

    public int getStackSize() {
        return stack_size;
    }

    public int getPreSize() {
        return preSize;
    }

    public RegPool getRegPool() {
        return regPool;
    }

    public String genTmp() {
        String tmp = "T_" + (tmp_num++);
        Word word = new Word(tmp, Key.TMP, -1);
        Symbol symbol = new Symbol("tmp", 0, word, table.getSymbolId());
        table.getFunction(funcName).addSymbol(symbol);
        symbol.setSize();
        return tmp;
    }

    public String genStr(String content) {
        String str = "str_" + (str_num++);
        midStrs.add(new MidStr(str, content));
        return str;
    }

    public String genLabel() {
        return "label_" + (label_num++);
    }

    public void startFuncDef(String funcName) {
        this.funcName = funcName;
        offset_sp = preSize;
    }

    public boolean isNumber(String value) {
        Pattern pattern = Pattern.compile("^(-)?\\d+");
        Matcher matcher = pattern.matcher(value);
        return matcher.matches();
    }

    public String genArrayIndex(Symbol symbol, String i, String j, String value) {
        if (value == null) {
            if (isNumber(i)) {
                int value1 = Integer.parseInt(i);
                if (isNumber(j)) {
                    int value2 = Integer.parseInt(j);
                    int value3 = value1 * symbol.getLen_2() + value2;
                    String tmp = genTmp();
                    addMidcode(new MidCode(OPType.LW_ARRAY, symbol.getName(), Integer.toString(value3), tmp));
                    return tmp;
                } else {
                    String tmp1 = genTmp();
                    String tmp2 = genTmp();
                    addMidcode(new MidCode(OPType.ADD, Integer.toString(value1 * symbol.getLen_2()), j, tmp1));
                    addMidcode(new MidCode(OPType.LW_ARRAY, symbol.getName(), tmp1, tmp2));
                    return tmp2;
                }
            } else {
                String tmp1 = genTmp();
                String tmp2 = genTmp();
                String tmp3 = genTmp();
                addMidcode(new MidCode(OPType.MULT, i, Integer.toString(symbol.getLen_2()), tmp1));
                addMidcode(new MidCode(OPType.ADD, j, tmp1, tmp2));
                addMidcode(new MidCode(OPType.LW_ARRAY, symbol.getName(), tmp2, tmp3));
                return tmp3;
            }
        } else {
            if (isNumber(i)) {
                int value1 = Integer.parseInt(i);
                if (isNumber(j)) {
                    int value2 = Integer.parseInt(j);
                    int value3 = value1 * symbol.getLen_2() + value2;
                    addMidcode(new MidCode(OPType.SW_ARRAY, symbol.getName(), Integer.toString(value3), value));
                } else {
                    String tmp1 = genTmp();
                    addMidcode(new MidCode(OPType.ADD, Integer.toString(value1 * symbol.getLen_2()), j, tmp1));
                    addMidcode(new MidCode(OPType.SW_ARRAY, symbol.getName(), tmp1, value));
                }
            } else {
                String tmp1 = genTmp();
                String tmp2 = genTmp();
                addMidcode(new MidCode(OPType.MULT, i, Integer.toString(symbol.getLen_2()), tmp1));
                addMidcode(new MidCode(OPType.ADD, j, tmp1, tmp2));
                addMidcode(new MidCode(OPType.SW_ARRAY, symbol.getName(), tmp2, value));
            }
            return "";
        }
    }

    public String getMidCode() {
        regPool = new RegPool();
        compUnit.genMidCode();
        StringBuilder sb = new StringBuilder();
        sb.append(globalStart).append("\n");
        for (MidCode globalDecl : globalDecls) {
            sb.append(globalDecl.toString()).append("\n");
        }
        sb.append(globalEnd).append("\n");
        sb.append(asciizStart).append("\n");
        for (MidStr str : midStrs) {
            sb.append(str.toString());
        }
        sb.append(asciizEnd).append("\n").append("\n");
        for (int i = 0; i < midCodes.size(); i++) {
            MidCode code = midCodes.get(i);
            if (code.getOpType() == OPType.FUNC_DECL) {
                sb.append(funcStart).append("\n");
            }
            if (Optimizer.isOptimise && code.getOpType() == OPType.DIV) {
                opDiv(i);
            }
            genTmpMap(i);
            sb.append(code).append("\n");
            if (code.getOpType() == OPType.RET_INT || code.getOpType() == OPType.RET_VOID) {
                sb.append(funcEnd).append("\n");
            }
        }
        sb.append(funcEnd);
        return sb.toString();
    }

    private void opDiv(int index) {
        String funcName = midCodes.get(index).getFuncName();
        String val1 = midCodes.get(index).getVal1();
        ArrayList<String> val2 = new ArrayList<>();
        ArrayList<String> dst = new ArrayList<>();
        int i = index;
        while (midCodes.get(i).getOpType() == OPType.DIV) {
            val2.add(midCodes.get(i).getVal2());
            dst.add(midCodes.get(i).getDst());
            i++;
        }
        if (val2.size() == 2) {
            return ;
        }
        String tmp1 = val2.get(0);
        for (int j = 1; j < val2.size(); j++) {
            String tmp2 = val2.get(j);
            String tmp3 = dst.get(j - 1);
            midCodes.set(index + j - 1, new MidCode(OPType.MULT, tmp1, tmp2, tmp3, funcName));
            tmp1 = tmp3;
        }
        i = val2.size() - 1;
        midCodes.set(index + i, new MidCode(OPType.DIV, val1, tmp1, dst.get(i), funcName));
    }

    private void genTmpMap(int index) {
        MidCode code = midCodes.get(index);
        if (code.getVal1() != null && code.getVal1().startsWith("T_")) {
            ArrayList<Integer> list = tmpMap.getOrDefault(code.getVal1(), new ArrayList<>());
            list.add(index);
            tmpMap.put(code.getVal1(), list);
        }
        if (code.getVal2() != null && code.getVal2().startsWith("T_")) {
            ArrayList<Integer> list = tmpMap.getOrDefault(code.getVal2(), new ArrayList<>());
            list.add(index);
            tmpMap.put(code.getVal2(), list);
        }
        if (code.getDst() != null && code.getDst().startsWith("T_")) {
            ArrayList<Integer> list = tmpMap.getOrDefault(code.getDst(), new ArrayList<>());
            list.add(index);
            tmpMap.put(code.getDst(), list);
        }
    }

    public HashMap<String, ArrayList<Integer>> getTmpMap() {
        return tmpMap;
    }
}

